//
//  CollectionModel.swift
//  Risac
//
//  Created by remy on 2018/8/8.
//

import UIKit

/// UICollectionView 的 dataSource 代理对象
public final class CollectionModel: NSObject, ListModel {
    public var sectionItems: [CollectionSectionItem] = []
    /// section索引-值列表
    public var sectionIndexTitles: [String]?
    /// section索引-值列表对应的section
    public var sectionForSectionIndexTitle: [Int]?
    private var registerSet: Set<String> = []
    
    public func set(_ cellItem: CollectionCellItem) {
        set([cellItem])
    }
    
    public func set(_ cellItems: [CollectionCellItem]) {
        guard cellItems.count > 0 else { return }
        if sectionItems.count == 0 {
            sectionItems.append(CollectionSectionItem())
        }
        sectionItems.last!.cellItems = cellItems
    }
    
    public func set(_ sectionItem: CollectionSectionItem) {
        set([sectionItem])
    }
    
    public func set(_ sectionItems: [CollectionSectionItem]) {
        guard sectionItems.count > 0 else { return }
        self.sectionItems = sectionItems
    }
    
    public func set(_ cellItem: CollectionCellItem, at indexPath: IndexPath) {
        guard let sectionItem = sectionItems[safe: indexPath.section] else { return }
        sectionItem.cellItems.safeSet(cellItem, at: indexPath.row)
    }
    
    public func set(_ sectionItem: CollectionSectionItem, at section: Int) {
        sectionItems.safeSet(sectionItem, at: section)
    }
    
    public func add(_ cellItem: CollectionCellItem, at section: Int? = nil) {
        add([cellItem], at: section)
    }
    
    public func add(_ cellItems: [CollectionCellItem], at section: Int? = nil) {
        guard cellItems.count > 0 else { return }
        if let section = section {
            sectionItems[safe: section]?.cellItems.append(contentsOf: cellItems)
        } else {
            if sectionItems.count == 0 {
                sectionItems.append(CollectionSectionItem())
            }
            sectionItems.last!.cellItems.append(contentsOf: cellItems)
        }
    }
    
    public func add(_ sectionItem: CollectionSectionItem) {
        add([sectionItem])
    }
    
    public func add(_ sectionItems: [CollectionSectionItem]) {
        guard sectionItems.count > 0 else { return }
        self.sectionItems.append(contentsOf: sectionItems)
    }
    
    public func insert(_ cellItem: CollectionCellItem, at indexPath: IndexPath) {
        return insert([cellItem], at: indexPath)
    }
    
    public func insert(_ cellItems: [CollectionCellItem], at indexPath: IndexPath) {
        guard cellItems.count > 0 else { return }
        sectionItems[safe: indexPath.section]?.cellItems.safeInsert(cellItems, at: indexPath.row)
    }
    
    public func insert(_ sectionItem: CollectionSectionItem, at section: Int) {
        insert([sectionItem], at: section)
    }
    
    public func insert(_ sectionItems: [CollectionSectionItem], at section: Int) {
        guard sectionItems.count > 0 else { return }
        self.sectionItems.safeInsert(sectionItems, at: section)
    }
    
    @discardableResult
    public func remove(at section: Int) -> Bool {
        sectionItems.safeRemove(at: section)
    }
    
    @discardableResult
    public func remove(at indexPath: IndexPath) -> Bool {
        if let sectionItem = sectionItems[safe: indexPath.section] {
            return sectionItem.cellItems.safeRemove(at: indexPath.row)
        }
        return false
    }
    
    public func removeAll() {
        sectionItems = []
    }
    
    public func cellItems(at section: Int? = nil) -> [CollectionCellItem] {
        if let section = section {
            return sectionItems[safe: section]?.cellItems ?? []
        }
        return sectionItems.reduce(into: [CollectionCellItem]()) {
            $0.append(contentsOf: $1.cellItems)
        }
    }
    
    public func cellItemsCount(at section: Int? = nil) -> Int {
        if let section = section {
            return sectionItems[safe: section]?.cellItems.count ?? 0
        }
        return sectionItems.reduce(0) { $0 + $1.cellItems.count }
    }
    
    public func cellItem(at indexPath: IndexPath) -> CollectionCellItem? {
        return sectionItems[safe: indexPath.section]?.cellItems[safe: indexPath.row]
    }
}

extension CollectionModel {
    private func sectionView(collectionView: UICollectionView, indexPath: IndexPath, kind: String) -> UICollectionReusableView {
        let identifier = UICollectionReusableView.metaTypeName
        if !registerSet.contains(identifier) {
            registerSet.insert(identifier)
            collectionView.register(UICollectionReusableView.self, forSupplementaryViewOfKind: kind, withReuseIdentifier: identifier)
        }
        return collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: identifier, for: indexPath)
    }
    
    private func sectionView(with sectionClass: CollectionSection.Type, collectionView: UICollectionView, sectionItem: CollectionSectionItem, kind: String, suffix: String) -> UICollectionReusableView {
        let identifier = sectionClass.metaTypeName + suffix
        if !registerSet.contains(identifier) {
            registerSet.insert(identifier)
            collectionView.register(sectionClass, forSupplementaryViewOfKind: kind, withReuseIdentifier: identifier)
        }
        let section = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: identifier, for: sectionItem.indexPath!)
        (section as! CollectionSection).updateBinding(sectionItem: sectionItem)
        return section
    }
    
    private func cellView(with cellClass: CollectionCell.Type, collectionView: UICollectionView, cellItem: CollectionCellItem) -> UICollectionViewCell {
        let identifier = cellClass.metaTypeName + cellItem.cellReuseIDSuffix
        if !registerSet.contains(identifier) {
            registerSet.insert(identifier)
            collectionView.register(cellClass, forCellWithReuseIdentifier: identifier)
        }
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: identifier, for: cellItem.indexPath!)
        (cell as! CollectionCell).updateBinding(cellItem: cellItem)
        return cell
    }
}

extension CollectionModel: UICollectionViewDataSource {
    public func numberOfSections(in collectionView: UICollectionView) -> Int {
        return sectionItems.count
    }

    public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        if let item = sectionItems[safe: section], item.showCellItems {
            return item.cellItems.count
        }
        return 0
    }
    
    public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        if let item = cellItem(at: indexPath) {
            if let cls = item.cellClass {
                item.indexPath = indexPath
                return cellView(with: cls, collectionView: collectionView, cellItem: item)
            }
        }
        fatalError()
    }
    
    public func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
        if kind == UICollectionView.elementKindSectionHeader {
            if let item = sectionItems[safe: indexPath.section], let cls = item.sectionHeaderClass {
                item.indexPath = indexPath
                return sectionView(with: cls, collectionView: collectionView, sectionItem: item, kind: kind, suffix: item.sectionHeaderReuseIDSuffix)
            }
        } else if kind == UICollectionView.elementKindSectionFooter {
            if let item = sectionItems[safe: indexPath.section], let cls = item.sectionFooterClass {
                item.indexPath = indexPath
                return sectionView(with: cls, collectionView: collectionView, sectionItem: item, kind: kind, suffix: item.sectionFooterReuseIDSuffix)
            }
        } else if kind == UICollectionView.elementKindSectionStage {
            if let item = sectionItems[safe: indexPath.section], let cls = item.sectionStageClass {
                item.indexPath = indexPath
                return sectionView(with: cls, collectionView: collectionView, sectionItem: item, kind: kind, suffix: item.sectionStageReuseIDSuffix)
            }
        }
        return sectionView(collectionView: collectionView, indexPath: indexPath, kind: kind)
    }
    
    public func indexTitles(for collectionView: UICollectionView) -> [String]? {
        return sectionIndexTitles
    }
    
    public func collectionView(_ collectionView: UICollectionView, indexPathForIndexTitle title: String, at index: Int) -> IndexPath {
        if let index = sectionForSectionIndexTitle?.firstIndex(of: index) {
            return IndexPath(row: 0, section: index)
        }
        return IndexPath(row: 0, section: -1)
    }
}
